package com.taskinception.model;

import android.content.ContentValues;
import android.database.Cursor;
import android.util.Log;

import com.taskinception.connectivity.DatabaseAdapter;

import java.util.ArrayList;
import java.util.Date;

/**
 * Model for {@code Task}s. It holds all the properties and gives acces to them.
 * The data has to be persisted using the
 * {@link com.taskinception.connectivity.DatabaseAdapter DatabaseAdapter} class,
 * for a {@code Task} has no access to the database.
 * 
 * @author Remy Parzinski
 */
public class Task {

    private long mId;
    private String mRemoteId;
    private String mTitle;
    private String mBody;
    private String mOwner;
    private long mCreationDate;
    private long mLastModifiedDate;
    private String mLastModifier;
    private boolean mDone;
    // TODO: Store the parent task as a Task object
    private long mParent;
    private ArrayList<Task> mChildren;
    private ArrayList<String> mSharedWith;

    public Task() {
	// TODO (remy):
	// setOwner(Secure.getString(TIApplication.getInstance()
	// .getContentResolver(), Secure.ANDROID_ID));
	setOwner("Remy");
	setCreationDate(new Date().getTime());
	setLastModifiedDate(getCreationDate());
	setLastModifier(getOwner());
	setDone(false);
	setParent(0);
	setTitle("");
	setBody("");
	generateRemoteId();
    }

    public Task(String title, String body) {
	this();
	setTitle(title);
	setBody(body);
    }

    public Task(String title, String body, Task parent) {
	this(title, body);
	setParent(parent.getId());
    }

    public Task(Cursor cursor) {
	this();
	setId(cursor.getLong(cursor
		.getColumnIndexOrThrow(DatabaseAdapter.TASK.ROWID)));
	setTitle(cursor.getString(cursor
		.getColumnIndex(DatabaseAdapter.TASK.TITLE)));
	setBody(cursor.getString(cursor
		.getColumnIndex(DatabaseAdapter.TASK.BODY)));
	setOwner(cursor.getString(cursor
		.getColumnIndex(DatabaseAdapter.TASK.OWNER)));
	setCreationDate(cursor.getLong(cursor
		.getColumnIndex(DatabaseAdapter.TASK.CREATED_DATE)));
	setLastModifiedDate(cursor.getLong(cursor
		.getColumnIndex(DatabaseAdapter.TASK.LAST_MODIFIED_DATE)));
	setLastModifier(cursor.getString(cursor
		.getColumnIndex(DatabaseAdapter.TASK.LAST_MODIFIER)));
	setDone(cursor.getLong(cursor
		.getColumnIndex(DatabaseAdapter.TASK.IS_DONE)) > 0);
	setParent(cursor.getLong(cursor
		.getColumnIndex(DatabaseAdapter.TASK.PARENT)));
    }

    @Override
    public String toString() {
	return getTitle();
    }

    /**
     * Convenience method for saving the data to the database.
     * 
     * @see com.taskinception.connectivity.DatabaseAdapter DatabaseAdapter
     * @see com.taskinception.connectivity.DatabaseAdapter.TASK
     *      DatabaseAdapter.TASK
     * @return ContentValues of the current state of the {@code Task}
     */
    public ContentValues getContentValues() {
	ContentValues values = new ContentValues();
	values.put(DatabaseAdapter.TASK.TITLE, mTitle);
	values.put(DatabaseAdapter.TASK.BODY, mBody);
	values.put(DatabaseAdapter.TASK.OWNER, mOwner);
	values.put(DatabaseAdapter.TASK.CREATED_DATE, mCreationDate);
	values.put(DatabaseAdapter.TASK.LAST_MODIFIED_DATE, mLastModifiedDate);
	values.put(DatabaseAdapter.TASK.LAST_MODIFIER, mLastModifier);
	values.put(DatabaseAdapter.TASK.IS_DONE, mDone);
	values.put(DatabaseAdapter.TASK.PARENT, mParent);
	values.put(DatabaseAdapter.TASK.REMOTE_ID, mRemoteId);
	return values;
    }

    /**
     * Print the current state to Logcat in the Verbose level
     */
    public void dump() {
	Log.v("TASK", "------------------------------------------------");
	Log.v("TASK", "Id: " + getId());
	Log.v("TASK", "Title: " + getTitle());
	Log.v("TASK", "Body: " + getBody());
	Log.v("TASK", "Owner: " + getOwner());
	Log.v("TASK", "Created: " + new Date(getCreationDate()).toString());
	Log.v("TASK",
		"Last modified: " + new Date(getLastModifiedDate()).toString());
	Log.v("TASK", "Done: " + isDone());
	Log.v("TASK", "Parent: " + getParent());
	Log.v("TASK", "Remote Id: " + getRemoteId());
	Log.v("TASK", "Children: " + getChildren().size());
    }

    /**
     * Move to another {@link Task} list
     * 
     * @param newParent
     *            {@code Task} to move to
     * @throws IllegalArgumentException
     *             if current task is main task list
     */
    public void move(Task newParent) throws IllegalArgumentException {
	if (getId() > 0) {
	    setParent(newParent.getId());
	} else {
	    throw new IllegalArgumentException(
		    "You are not allowed to move the main task list");
	}
    }

    /**
     * Get the progress of the task. If {@code mRootTask} has children (is a
     * list), show the progress of the list in the format of {@code (x/y) z%}
     * where:
     * 
     * <pre>
     * x: number of finished tasks in respective task list
     * y: number of tasks in respective task list
     * z: progress displayed in percentage
     * </pre>
     * 
     * @return A formatted {@code String} in the format of {@code (x/y) z%}
     */
    public String getProgress() {
	float percentage = (float) Math.floor((getChildrenDone() * 100.0F)
		/ getChildren().size());
	if (percentage == Float.NaN || percentage == Double.NaN) {
	    percentage = 100.0F;
	}
	return String.format("(%d/%d) %.0f%%", getChildrenDone(), getChildren()
		.size(), percentage);
    }

    /**
     * Get the amount of children that have been marked as "done"
     * 
     * @return amount of children marked as "done". If there are no children,
     *         return 0
     */
    private int getChildrenDone() {
	int childrenDone = 0;
	if (isList()) {
	    for (Task child : getChildren()) {
		if (child.isDone()) {
		    childrenDone++;
		}
	    }
	}
	return childrenDone;
    }

    /**
     * Check if there are children that are marked as done
     * 
     * @return if there are children that are marked as done
     */
    public boolean hasFinishedChildren() {
	return getChildrenDone() > 0;
    }

    /**
     * @return the id
     */
    public long getId() {
	return mId;
    }

    /**
     * @param id
     *            the id to set
     */
    public void setId(long id) {
	if (mId != -1)
	    this.mId = id;
    }

    /**
     * @return the remote id
     */
    public String getRemoteId() {
	generateRemoteId();
	return mRemoteId;
    }

    /**
     * Generate the remote id
     */
    private void generateRemoteId() {
	if (mRemoteId == null || mRemoteId.isEmpty()) {
	    mRemoteId = mOwner + " " + Long.toString(mId);
	}
    }

    /**
     * @return the title
     */
    public String getTitle() {
	return mTitle;
    }

    /**
     * @param title
     *            the title to set
     */
    public void setTitle(String title) {
	this.mTitle = title;
    }

    /**
     * @return the body
     */
    public String getBody() {
	return mBody;
    }

    /**
     * @param body
     *            the body to set
     */
    public void setBody(String body) {
	this.mBody = body;
    }

    /**
     * @return the owner
     */
    public String getOwner() {
	return mOwner;
    }

    /**
     * @param owner
     *            the owner to set
     */
    public void setOwner(String owner) {
	this.mOwner = owner;
    }

    /**
     * @return the creationDate
     */
    public long getCreationDate() {
	return mCreationDate;
    }

    /**
     * @param creationDate
     *            the creationDate to set
     */
    public void setCreationDate(long creationDate) {
	this.mCreationDate = creationDate;
    }

    /**
     * @return the lastModifiedDate
     */
    public long getLastModifiedDate() {
	return mLastModifiedDate;
    }

    /**
     * @param lastModifiedDate
     *            the lastModifiedDate to set
     */
    public void setLastModifiedDate(long lastModifiedDate) {
	this.mLastModifiedDate = lastModifiedDate;
    }

    /**
     * @return the lastModifier
     */
    public String getLastModifier() {
	return mLastModifier;
    }

    /**
     * @param lastModifier
     *            the lastModifiedDate to set
     */
    public void setLastModifier(String lastModifier) {
	this.mLastModifier = lastModifier;
    }

    /**
     * @return whether it and all subtasks, if any, are marked as "done"
     */
    public boolean isDone() {
	return mDone && getChildren().size() == getChildrenDone();
    }

    /**
     * @param done
     *            wether to check it as done
     */
    public void setDone(boolean done) {
	this.mDone = done;
    }

    /**
     * @return the parent
     */
    public long getParent() {
	return mParent;
    }

    /**
     * @param parent
     *            the parent to set
     * @throws IllegalArgumentException
     *             if given parent < 0
     */
    public void setParent(long parent) {
	if (parent < 0) {
	    throw new IllegalArgumentException(
		    "Parent has to be 0 for top level, or higher");
	}
	this.mParent = parent;
    }

    /**
     * @return true if has children
     */
    public boolean isList() {
	return !getChildren().isEmpty();
    }

    /**
     * @return the children
     */
    public ArrayList<Task> getChildren() {
	resetChildren();
	return mChildren;
    }

    /**
     * @param children
     *            the children to set
     */
    public void setChildren(ArrayList<Task> children) {
	resetChildren();
	this.mChildren = children;
    }

    /**
     * Make sure there is an ArrayList available
     */
    private void resetChildren() {
	if (mChildren == null) {
	    mChildren = new ArrayList<Task>();
	}
    }

    /**
     * @return true if is shared
     */
    public boolean isShared() {
	return mSharedWith != null && !getSharedWith().isEmpty();
    }

    /**
     * @return the shared with list
     */
    public ArrayList<String> getSharedWith() {
	resetSharedWith();
	return mSharedWith;
    }

    /**
     * @param children
     *            the children to set
     */
    public void setSharedWith(ArrayList<String> sharedWith) {
	resetSharedWith();
	this.mSharedWith = sharedWith;
    }

    /**
     * Make sure there is an ArrayList available
     */
    private void resetSharedWith() {
	if (mSharedWith == null) {
	    mSharedWith = new ArrayList<String>();
	}
    }

    /**
     * Tries to find direct child with remoteID as in Task.
     * 
     * @param pattern
     *            Task to be compared with - received from sharing
     * @return Task in our Task tree matching the remoteID
     */
    public Task findChildWithRemoteId(Task pattern) {
	Task matchTask = null;
	Log.e("Task", "findChildWithRemoteId: pattern: " + pattern.getRemoteId() + " title: " + pattern.getTitle());
	for (Task task : this.getChildren()) {
	//    if (task.isShared()) {
			Log.e("Task", "findChildWithRemoteId: child: " + task.getRemoteId() + " title: " + task.getTitle());
			if (task.getOwner().equals(pattern.getOwner())) {
			    if (task.getRemoteId().equals(pattern.getRemoteId())) {
				matchTask = task;
				break;
			    }
			}
	//    }
	}

	return matchTask;
    }

}
